package org.dftproject.genesis.ui.pages.summary;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dftproject.genesis.data.IAttribute;
import org.dftproject.genesis.data.genealogy.Events;
import org.dftproject.genesis.data.genealogy.GenealogyConstants;
import org.dftproject.genesis.data.genealogy.GenealogyUtils;
import org.dftproject.genesis.data.genealogy.IEvent;
import org.dftproject.genesis.data.genealogy.IInstant;
import org.dftproject.genesis.data.genealogy.IName;
import org.dftproject.genesis.data.genealogy.INote;
import org.dftproject.genesis.data.genealogy.IPerson;
import org.dftproject.genesis.data.genealogy.IRole;
import org.dftproject.genesis.data.genealogy.Sex;
import org.dftproject.genesis.data.genealogy.impl.MergedPerson;
import org.dftproject.genesis.ui.Activator;
import org.dftproject.genesis.ui.figures.Caption;
import org.dftproject.genesis.ui.figures.ColumnData;
import org.dftproject.genesis.ui.figures.ColumnLayout;
import org.dftproject.genesis.ui.figures.UniformSizeGroup;
import org.dftproject.genesis.ui.pages.AbstractPagePart;
import org.dftproject.genesis.ui.pages.IPageInput;
import org.dftproject.genesis.ui.pages.IPageSite;
import org.dftproject.genesis.ui.pages.PageInitException;
import org.dftproject.genesis.ui.pages.PageManager;
import org.dftproject.genesis.ui.pages.circlediagram.CircleDiagramIconFigure;
import org.dftproject.genesis.ui.pages.circlediagram.CircleDiagramPage;
import org.dftproject.genesis.ui.pages.circlediagram.CircleDiagramPageInput;
import org.dftproject.genesis.ui.pages.pedigree.PedigreePage;
import org.dftproject.genesis.ui.pages.pedigree.PedigreePageInput;
import org.dftproject.genesis.ui.pages.pedigree.icon.PedigreeIconFigure;
import org.dftproject.genesis.ui.pages.summary.details.DetailFigure;
import org.dftproject.genesis.ui.pages.summary.details.DetailsPanel;
import org.dftproject.genesis.ui.pages.summary.details.EventFigure;
import org.dftproject.genesis.ui.pages.summary.details.FamilyFigure;
import org.dftproject.genesis.ui.pages.summary.details.FigureListViewer;
import org.dftproject.genesis.ui.pages.summary.details.IFigureProvider;
import org.dftproject.genesis.ui.pages.summary.details.NameFigure;
import org.dftproject.genesis.ui.pages.summary.details.NoteFigure;
import org.dftproject.genesis.ui.pages.summary.details.SexFigure;
import org.dftproject.genesis.ui.pages.summary.sources.FootnoteContainer;
import org.dftproject.genesis.ui.widgets.gallery.Gallery;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.Panel;
import org.eclipse.draw2d.ToolbarLayout;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

import com.eekbom.utils.Strings;

public class SummaryPage extends AbstractPagePart {

	private static final Log log = LogFactory.getLog(SummaryPage.class);

	public static final String ID = SummaryPage.class.getName();

	private boolean updateScheduled;
	private final Queue<Object> updateQueue = new LinkedList<Object>();
	private final Runnable uiRunnable = new Runnable() {

		public void run() {
			updateScheduled = false;
			update();
		}

	};

	private FigureCanvas canvas;
	
	private FootnoteContainer footnoteContainer;

	private Caption caption;
	private FigureListViewer detailsViewer;

	private FigureListViewer familiesViewer;

	private final PersonSelectionProvider selectionProvider = new PersonSelectionProvider();

	private MergedPerson person;

	public void init(IPageSite site, IPageInput input) throws PageInitException {
		if (!(input instanceof SummaryPageInput))
			throw new PageInitException("input must be a " + SummaryPageInput.class.getName());
		this.setInput(input);
		site.setSelectionProvider(selectionProvider);
		selectionProvider.changed(((SummaryPageInput) input).getPerson());

		person = getMergedPerson(getSummaryPageInput().getPerson());
	}

	protected SummaryPageInput getSummaryPageInput() {
		return ((SummaryPageInput) this.getInput());
	}

	protected MergedPerson getMergedPerson(IPerson person) {
		if (person instanceof MergedPerson || person == null)
			return (MergedPerson) person;
		MergedPerson mergePerson = new MergedPerson(person);
		return mergePerson;
	}

	@Override
	public void createPartControl(Composite parent) {
		Composite container = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = layout.marginTop = layout.marginRight = layout.marginBottom = layout.marginLeft = layout.horizontalSpacing = 0;
		layout.numColumns = 2;
		container.setLayout(layout);

		createGallery(container);

		Composite body = new Composite(container, SWT.NONE);
		layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = layout.marginTop = layout.marginRight = layout.marginBottom = layout.marginLeft = layout.verticalSpacing = 0;
		body.setLayout(layout);
		body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		createContents(body);
	}

	protected void createGallery(Composite parent) {
		Gallery gallery = new Gallery(parent);
		gallery.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));

		final IPerson person = ((SummaryPageInput) this.getInput()).getPerson();

		gallery.addItem(Activator.getImageDescriptor("icons/user.png").createImage(), true, "Summary", null);
		gallery.addItem(new PedigreeIconFigure(), false, "Pedigree", new Runnable() {

			public void run() {
				IWorkbench workbench = PlatformUI.getWorkbench();
				IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
				IWorkbenchPage workbenchPage = window.getActivePage();
				PageManager.getDefault().openPage(workbenchPage, new PedigreePageInput(person), PedigreePage.ID, true, false);
			}

		});
		gallery.addItem(new CircleDiagramIconFigure(), false, "Circle Diagram", new Runnable() {

			public void run() {
				IWorkbench workbench = PlatformUI.getWorkbench();
				IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
				IWorkbenchPage workbenchPage = window.getActivePage();
				PageManager.getDefault().openPage(workbenchPage, new CircleDiagramPageInput(person), CircleDiagramPage.ID, true, false);
			}

		});
	}

	protected void createContents(Composite parent) {
		// Create the main Draw2D canvas

		canvas = new FigureCanvas(parent);
		canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		// Create the root figure

		Panel container = new Panel();
		container.setBackgroundColor(ColorConstants.listBackground);
		ToolbarLayout layout = new ToolbarLayout(false);
		layout.setSpacing(20);
		container.setLayoutManager(layout);
		container.setBorder(new MarginBorder(10));
		canvas.setContents(container);
		canvas.getViewport().setContentsTracksWidth(true);

		caption = new Caption("(unknown)");
		container.add(caption);

		Figure columns = new Figure();
		columns.setLayoutManager(new ColumnLayout(10));
		container.add(columns);
		
		footnoteContainer = new FootnoteContainer(canvas.getViewport());
		container.add(footnoteContainer);

		createDetails(columns);
		createFamilies(columns);

		Job job = new Job("Populate page") {

			@Override
			protected IStatus run(IProgressMonitor monitor) {
				for (IAttribute attribute : person.getAttributes())
					scheduleUIUpdate(attribute);

				Set<IRole> roles = person.getRoles();
				for (IRole role : roles)
					scheduleUIUpdate(role);

				return Status.OK_STATUS;
			}

		};
		job.schedule();
	}

	protected void createDetails(IFigure parent) {
		IFigure detailsPanel = new DetailsPanel();
		parent.add(detailsPanel, new ColumnData(1));

		detailsViewer = new FigureListViewer(detailsPanel);
		detailsViewer.setContentProvider(new IStructuredContentProvider() {

			public Object[] getElements(Object inputElement) {
				return new Object[0];
			}

			public void dispose() {
			}

			public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			}

		});
		detailsViewer.setFigureProvider(new IFigureProvider() {

			private final UniformSizeGroup uniformSizeGroup = new UniformSizeGroup();

			public IFigure getFigure(Object element) {
				if (element instanceof IAttribute) {
					IAttribute attribute = (IAttribute) element;
					Object value = attribute.getValue();
					if (value instanceof IName) {
						return new DetailFigure(getLabel(attribute), new NameFigure((IName) value), uniformSizeGroup);
					} else if (value instanceof Sex) {
						SexFigure figure = new SexFigure((Sex) value, footnoteContainer);
						for (IAttribute sourceAttribute : attribute.getAttributes(GenealogyConstants.source))
							figure.addSource(sourceAttribute);
						return new DetailFigure(getLabel(attribute), figure, uniformSizeGroup);
					} else if (value instanceof INote) {
						NoteFigure figure = new NoteFigure(footnoteContainer);
						figure.setText(((INote) value).getContents());
						for (IAttribute sourceAttribute : attribute.getAttributes(GenealogyConstants.source))
							figure.addSource(sourceAttribute);
						return new DetailFigure(getLabel(attribute), figure, uniformSizeGroup);
					} else if (value instanceof String) {
						NoteFigure figure = new NoteFigure(footnoteContainer);
						figure.setText((String) value);
						for (IAttribute sourceAttribute : attribute.getAttributes(GenealogyConstants.source))
							figure.addSource(sourceAttribute);
						return new DetailFigure(getLabel(attribute), figure, uniformSizeGroup);
					}
				} else if (element instanceof IRole) {
					IRole role = (IRole) element;
					return new DetailFigure(getLabel(role), new EventFigure(role), uniformSizeGroup);
				}
				return null;
			}

		});
		detailsViewer.setComparator(new ViewerComparator() {

			@Override
			public int compare(Viewer viewer, Object o1, Object o2) {
				if (o2 instanceof IAttribute && o2 instanceof IAttribute) {
					IAttribute left = (IAttribute) o1;
					IAttribute right = (IAttribute) o2;
					if (!left.getName().equals(right.getName())) {
						if (GenealogyConstants.name.equals(left.getName()))
							return -1;
						if (GenealogyConstants.name.equals(right.getName()))
							return 1;
						if (GenealogyConstants.sex.equals(left.getName()))
							return -1;
						if (GenealogyConstants.sex.equals(right.getName()))
							return 1;
						int c = Strings.compareNatural(getLabel(left), getLabel(right));
						if (c != 0)
							return c;
					}
					return left.getValue().hashCode() - right.getValue().hashCode();
				} else if (o1 instanceof IRole && o2 instanceof IRole) {
					IRole left = (IRole) o1;
					IRole right = (IRole) o2;

					IInstant instant1 = (IInstant) GenealogyUtils.getValue(left.getEvent(), Events.instant, IInstant.class);
					IInstant instant2 = (IInstant) GenealogyUtils.getValue(right.getEvent(), Events.instant, IInstant.class);
					if (instant1 != null && instant2 != null) {
						Long millis1 = instant1.getMillis();
						Long millis2 = instant2.getMillis();
						if (millis1 != null && millis2 != null) {
							if (millis1 > millis2)
								return 1;
							if (millis1 < millis2)
								return -1;
						}
					}

					if (!left.getType().equals(right.getType())) {
						if (GenealogyConstants.Child.equals(left.getType()))
							return -1;
						if (GenealogyConstants.Child.equals(right.getType()))
							return 1;
						if (GenealogyConstants.Christened.equals(left.getType()))
							return -1;
						if (GenealogyConstants.Christened.equals(right.getType()))
							return 1;
						if (GenealogyConstants.Buried.equals(left.getType()))
							return 1;
						if (GenealogyConstants.Buried.equals(right.getType()))
							return -1;
						if (GenealogyConstants.Deceased.equals(left.getType()))
							return 1;
						if (GenealogyConstants.Deceased.equals(right.getType()))
							return -1;
						int c = Strings.compareNatural(getLabel(left), getLabel(right));
						if (c != 0)
							return c;
					}
					return left.getEvent().hashCode() - right.getEvent().hashCode();
				} else if (o1 instanceof IAttribute) {
					return -1;
				} else {
					return 1;
				}
			}

		});
		detailsViewer.setInput(getInput());
	}

	protected void createFamilies(IFigure parent) {
		IFigure familiesPanel = new DetailsPanel();
		parent.add(familiesPanel, new ColumnData());

		familiesViewer = new FigureListViewer(familiesPanel);
		familiesViewer.setContentProvider(new IStructuredContentProvider() {

			public Object[] getElements(Object inputElement) {
				return new Object[0];
			}

			public void dispose() {
			}

			public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			}

		});
		familiesViewer.setFigureProvider(new IFigureProvider() {

			public IFigure getFigure(Object element) {
				if (element instanceof IPerson) {
					IPerson spouse = (IPerson) element;
					return new FamilyFigure(spouse, person);
				}
				return null;
			}

		});
		familiesViewer.setComparator(new ViewerComparator() {

			@Override
			public int compare(Viewer viewer, Object o1, Object o2) {
				return o1.hashCode() - o2.hashCode();
			}

		});
		familiesViewer.setInput(getInput());
	}

	protected String getLabel(IAttribute attribute) {
		if (GenealogyConstants.name.equals(attribute.getName()))
			return "Name:";
		if (GenealogyConstants.sex.equals(attribute.getName()))
			return "Sex:";
		if (GenealogyConstants.note.equals(attribute.getName()))
			return "Note:";
		if (GenealogyConstants.afn.equals(attribute.getName()))
			return "Ancestral File Number:";
		if (GenealogyConstants.uid.equals(attribute.getName()))
			return "UID:";
		return "Unknown:";
	}

	protected String getLabel(IRole role) {
		if (GenealogyConstants.Child.equals(role.getType()))
			return "Birth:";
		if (GenealogyConstants.Christened.equals(role.getType()))
			return "Christening:";
		if (GenealogyConstants.Parent.equals(role.getType()))
			return "Child:";
		if (GenealogyConstants.Father.equals(role.getType()))
			return "Child:";
		if (GenealogyConstants.Mother.equals(role.getType()))
			return "Child:";
		if (GenealogyConstants.Spouse.equals(role.getType()))
			return "Marriage:";
		if (GenealogyConstants.Resided.equals(role.getType()))
			return "Residence:";
		if (GenealogyConstants.Immigrant.equals(role.getType()))
			return "Immigrated:";
		if (GenealogyConstants.Censused.equals(role.getType()))
			return "Census:";
		if (GenealogyConstants.Deceased.equals(role.getType()))
			return "Death:";
		if (GenealogyConstants.Buried.equals(role.getType()))
			return "Burial:";
		return "Unknown:";
	}

	@Override
	public void dispose() {
	}

	@Override
	public void setFocus() {
		canvas.setFocus();
	}

	protected void scheduleUIUpdate(Object item) {
		synchronized (updateQueue) {
			updateQueue.add(item);
			if (!updateScheduled) {
				updateScheduled = true;
				Display.getDefault().asyncExec(uiRunnable);
			}
		}
	}

	@SuppressWarnings("unchecked")
	protected void update() {
		synchronized (updateQueue) {
			Object item;
			while ((item = updateQueue.poll()) != null) {
				if (item instanceof IAttribute) {
					IAttribute attribute = (IAttribute) item;
					if (GenealogyConstants.name.equals(attribute.getName())) {
						IName name = (IName) attribute.getValue();
						if (name != null)
							caption.setText(GenealogyUtils.stringFromName(name));
					} else {
						detailsViewer.add(attribute);
					}
				} else if (item instanceof IRole) {
					IRole role = (IRole) item;
					if (GenealogyConstants.Spouse.equals(role.getType())) {
						IEvent event = role.getEvent();
						for (IRole spouseRole : event.getRoles(GenealogyConstants.Spouse)) {
							if (!person.sameAs(spouseRole.getPerson()))
								familiesViewer.add(spouseRole.getPerson());
						}
					} else if (GenealogyConstants.Parent.equals(role.getType()) || GenealogyConstants.Father.equals(role.getType()) || GenealogyConstants.Mother.equals(role.getType())) {
						IEvent event = role.getEvent();

						IPerson otherParent = null;
						for (IRole parentRole : event.getRoles(new String[] { GenealogyConstants.Parent, GenealogyConstants.Father, GenealogyConstants.Mother })) {
							if (!person.sameAs(parentRole.getPerson()))
								otherParent = parentRole.getPerson();
						}

						if (otherParent == null) {
							// TODO handle single parent case
							log.info("No other parent for event " + event);
						} else {
							for (IRole childRole : event.getRoles(GenealogyConstants.Child)) {
								int index = familiesViewer.getElementIndex(otherParent);
								if (index < 0) {
									familiesViewer.add(otherParent);
									index = familiesViewer.getElementIndex(otherParent);
								}
								IFigure figure = ((List<IFigure>) familiesViewer.getContainer().getChildren()).get(index);
								if (figure instanceof FamilyFigure)
									((FamilyFigure) figure).addChild(childRole);
							}
						}
					} else {
						detailsViewer.add(role);
					}
				}
			}

			canvas.getContents().invalidate();
		}
	}

	protected class PersonSelectionProvider implements ISelectionProvider {

		private final Set<ISelectionChangedListener> listeners = Collections.synchronizedSet(new HashSet<ISelectionChangedListener>());
		private ISelection selection;

		public void addSelectionChangedListener(ISelectionChangedListener listener) {
			listeners.add(listener);
		}

		public ISelection getSelection() {
			return selection;
		}

		public void removeSelectionChangedListener(ISelectionChangedListener listener) {
			listeners.add(listener);
		}

		public void setSelection(ISelection selection) {
			if (this.selection == selection)
				return;
			this.selection = selection;

			synchronized (listeners) {
				if (!listeners.isEmpty()) {
					SelectionChangedEvent event = new SelectionChangedEvent(this, selection);

					for (ISelectionChangedListener listener : listeners)
						listener.selectionChanged(event);
				}
			}
		}

		public void changed(IPerson person) {
			setSelection(new StructuredSelection(person));
		}

	}

}
